#include "View.h"
#include "Scene.h"
#include "Palette.h"
#include "RulerBar.h"
#include "GridModel.h"
#include "GraphicsCrossHair.h"
#include "GraphicsGrid.h"
#include "Settings.h"

#include <QtMath>
#include <QOpenGLWidget>
#include <QWheelEvent>
#include <QGraphicsSceneMouseEvent>
#include <QGridLayout>
#include <QScrollBar>
#include <QDebug>

namespace LeGraphicsView
{


View::View(QWidget *parent):
        QGraphicsView(parent),
        m_hardwareAccelerationEnabled(false),
        m_rulerEnabled(false),
        m_gridModel(new GridModel(this)),
        m_topLeftWidget(new QWidget),
        m_horizontalRuler(new RulerBar(RulerBar::Horizontal)),
        m_topRightWidget(new QWidget),
        m_verticalRuler(new RulerBar(RulerBar::Vertical)),
        m_bottomLeftWidget(new QWidget),
        m_gridEnabled(false),
        m_grid(new GraphicsGrid),
        m_originCrossHair(new GraphicsCrossHair),
        m_cursorCrossHair(new GraphicsCrossHair)
    {
        setupView();
        setupGrid();
        setupLayout();
        applyDefaultSettings();
    }

    View::~View()
    {

    }

    Scene *View::scene() const
    {
        return static_cast<Scene *>(QGraphicsView::scene());
    }

    void View::setScene(Scene *scene)
    {
        QGraphicsView::setScene(scene);
        scene->installEventFilter(this);
        m_gridModel->setSceneRect(visibleSceneRect());
    }

    void View::zoomIn(QPointF pos, qreal factor)
    {
        QPoint viewPos = mapFromScene(pos);
        scaleView(factor);
        pos -= mapToScene(viewPos);
        translateView(-pos.x(), -pos.y());
    }

    void View::scaleView(qreal scaleFactor)
    {
        qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
        if (factor < 0.1 || factor > 100)
        {
            return;
        }

        scale(scaleFactor, scaleFactor);
        m_gridModel->setSceneRect(visibleSceneRect());
    }

    void View::translateView(qreal dx, qreal dy)
    {
        translate(dx, dy);
        m_gridModel->setSceneRect(visibleSceneRect());
    }

    void View::drawBackground(QPainter *painter, const QRectF &rect)
    {
        painter->fillRect(rect, QBrush(m_palette.backgroundHighlight()));
        painter->fillRect(rect.intersected(sceneRect()), QBrush(m_palette.background()));
        m_grid->paint(painter, rect);
    }

    void View::drawForeground(QPainter *painter, const QRectF &rect)
    {
        m_originCrossHair->paint(painter, rect);
        m_cursorCrossHair->paint(painter, rect);
    }

    QRectF View::visibleSceneRect() const
    {
        QPointF topLeft = mapToScene(QPoint(0, 0));
        QPointF bottomRight = mapToScene(QPoint(viewport()->width(), viewport()->height()));
        return QRectF(topLeft, bottomRight);
    }

    void View::setVisibleSceneRect(const QRectF &rect)
    {
        fitInView(rect, Qt::KeepAspectRatio);
    }

    void View::updateForeground()
    {
        invalidateScene(visibleSceneRect(), QGraphicsScene::ForegroundLayer);
        viewport()->update();
    }

    void View::updateBackground()
    {
        invalidateScene(visibleSceneRect(), QGraphicsScene::BackgroundLayer);
        viewport()->update();
    }

    void View::setCursorPos(const QPointF &scenePos)
    {
        m_cursorCrossHair->setPos(scenePos);
        m_horizontalRuler->setCursorPosition(scenePos);
        m_verticalRuler->setCursorPosition(scenePos);

        updateForeground();
    }

    QPointF View::cursorPos() const
    {
        return m_cursorCrossHair->pos();
    }

    void View::setOriginPos(const QPointF &scenePos)
    {
        m_originCrossHair->setPos(scenePos);
        // TBD: need to update ruler and grid?

        updateForeground();
    }

    QPointF View::originPos() const
    {
        return  m_originCrossHair->pos();
    }

    // TBD: consider letting the scene manage cursor and origin?
    // TBD: condider cursor and origin being foreground items, or even plain QGraphicsItem?
    // TBD: The view still need snap info: update ruler + snap decoration
    // TBD: if snap srategy tells snap point and used items  they we can use QGEffect for highlighting
    // TBD: For alignment snap we want to draw Hor/Vert/Obkique lines, same either plan QGI or FG decorationPainter
    // TBD: use range for QGI->type() to differentiate b/w QGI for:
    //   - symbol model item
    //   - temp items
    //   - decoration items (BG or FG)
    bool View::eventFilter(QObject *watched, QEvent *event)
    {
        if (watched == scene())
        {
            if (event->type() == QEvent::GraphicsSceneMouseMove)
            {
                auto gsmEvent = static_cast<QGraphicsSceneMouseEvent*>(event);
                setCursorPos(gsmEvent->scenePos());
            }
        }
        else if (watched == horizontalScrollBar())
        {
            if (event->type() == QEvent::Show)
            {
                m_bottomLeftWidget->setFixedHeight(horizontalScrollBar()->height());
            }
            else if (event->type() == QEvent::Hide)
            {
                m_bottomLeftWidget->setFixedHeight(0);
            }
        }
        else if (watched == verticalScrollBar())
        {
            if (event->type() == QEvent::Show)
            {
                m_topRightWidget->setFixedWidth(verticalScrollBar()->width());
            }
            else if (event->type() == QEvent::Hide)
            {
                m_topRightWidget->setFixedWidth(0);
            }
        }

        return QGraphicsView::eventFilter(watched, event);
    }


    void View::resizeEvent(QResizeEvent *event)
    {
        QGraphicsView::resizeEvent(event);

        if (scene() == nullptr)
        {
            return;
        }

        m_gridModel->setSize(viewport()->size());
        m_gridModel->setSceneRect(visibleSceneRect());
    }

    void View::scrollContentsBy(int dx, int dy)
    {
        QGraphicsView::scrollContentsBy(dx, dy);
        m_gridModel->setSceneRect(visibleSceneRect());
    }

    void View::wheelEvent(QWheelEvent *event)
    {
        if (!event->modifiers().testFlag(Qt::ControlModifier))
        {
            // While doing pan view (mid-button), disable wheel event
            // This improve usability for sensitive "vertical wheel" mouses
            // Where you can inadvertly push the wheel sideway while holding
            // the wheel down and the wheel is as well the "mid button"
            if (!event->buttons().testFlag(Qt::MidButton))
            {
                QGraphicsView::wheelEvent(event);
            }
            return;
        }

        QPointF pos = mapToScene(event->pos());
        qreal factor = qPow(2.0, -event->delta() / 240.0);
        zoomIn(pos, factor);
    }

    void View::setPalette(Palette palette)
    {
        m_palette = palette;
        applyPalette();
        emit settingsChanged();
    }

    Palette View::palette() const
    {
        return m_palette;
    }

    // TODO: get notified by scene signal paletteChanged() and use scene()->palette()
    void View::applyPalette()
    {
        m_topLeftWidget->setStyleSheet(QString("background-color:%1;").arg(
                                          m_palette.backgroundHighlight().name()));
        m_horizontalRuler->setBackgroundColor(m_palette.backgroundHighlight());
        m_verticalRuler->setBackgroundColor(m_palette.backgroundHighlight());
        m_horizontalRuler->setForegroundColor(m_palette.primaryContent());
        m_verticalRuler->setForegroundColor(m_palette.primaryContent());

        m_cursorCrossHair->setColor(m_palette.green());
        m_originCrossHair->setColor(m_palette.red());

        m_grid->setGridColor(m_palette.secondaryContent());

        updateBackground();
        updateForeground();
    }

    void View::setHardwareAccelerationEnabled(bool enabled)
    {
        if (m_hardwareAccelerationEnabled == enabled)
        {
            return;
        }

        // Breaks mouse tracking, maybe need to call setMouseTracking after
        // setViewPort, and maybe even more things....
#if 0
        if (m_hardwareAccelerationEnabled)
        {
            setViewport(new QOpenGLWidget);
        }
        else
        {
            setViewport(new QWidget);
        }
#endif
        emit settingsChanged();
    }

    bool View::hardwareAccelerationEnabled() const
    {
        return m_hardwareAccelerationEnabled;
    }

    void View::setRulerEnabled(bool enabled)
    {
        if (m_rulerEnabled == enabled)
        {
            return;
        }

        m_rulerEnabled = enabled;

        if (m_rulerEnabled)
        {
            m_topLeftWidget->show();
            m_horizontalRuler->show();
            m_topRightWidget->show();
            m_verticalRuler->show();
            m_bottomLeftWidget->show();
            setViewportMargins(RulerBar::BREADTH, RulerBar::BREADTH, 0, 0);
        }
        else
        {
            m_topLeftWidget->hide();
            m_horizontalRuler->hide();
            m_topRightWidget->hide();
            m_verticalRuler->hide();
            m_bottomLeftWidget->hide();
            setViewportMargins(0, 0, 0, 0);
        }

        emit settingsChanged();
    }

    bool View::rulerEnabled() const
    {
        return m_rulerEnabled;
    }

    void View::setGridEnabled(bool enabled)
    {
        if (m_gridEnabled == enabled)
        {
            return;
        }

        m_gridEnabled = enabled;
        updateBackground();

        emit settingsChanged();
    }

    bool View::gridEnabled() const
    {
        return m_gridEnabled;
    }

    void View::setMinimalGridSize(int pixels)
    {
        m_gridModel->setMinimumTickSpacing(pixels);
        emit settingsChanged();
    }

    int View::minimalGridSize() const
    {
        return m_gridModel->minimumTickSpacing();
    }

    void View::setGridCoarseMultiplier(int multiplier)
    {
        m_gridModel->setCoarseMultiplier(multiplier);
        emit settingsChanged();
    }

    int View::gridCoarseMultiplier() const
    {
        return m_gridModel->coarseMultiplier();
    }

    void View::setGridCoarseLineStyle(Qt::PenStyle style)
    {
        //m_grid->setCoarseLineStyle(style);
        updateBackground();
        emit settingsChanged();
    }

    Qt::PenStyle View::gridCoarseLineStyle() const
    {
        return Qt::SolidLine; //m_grid->coarseLineStyle();
    }

    void View::setGridFineLineStyle(Qt::PenStyle style)
    {
        //m_grid->setFineLineStyle(style);
        updateBackground();
        emit settingsChanged();
    }

    Qt::PenStyle View::gridFineLineStyle() const
    {
        return Qt::SolidLine; //m_grid->fineLineStyle();
    }

    void View::setMouseCursor(View::MouseCursor cursor)
    {
        m_cursorCrossHair->setStyle(GraphicsCrossHair::Style(cursor));
        updateForeground();
        emit settingsChanged();
    }

    View::MouseCursor View::mouseCursor() const
    {
        return MouseCursor(m_cursorCrossHair->style());
    }

    void View::setOriginMark(View::OriginMark mark)
    {
        m_originCrossHair->setStyle(GraphicsCrossHair::Style(mark));
        updateForeground();
        emit settingsChanged();
    }

    View::OriginMark View::originMark() const
    {
        return OriginMark(m_originCrossHair->style());
    }

    const GridModel *View::gridModel() const
    {
        return m_gridModel;
    }

    void View::applySettings(const Settings &settings)
    {
        setRulerEnabled(settings.rulerEnabled);
        setGridEnabled(settings.gridEnabled);
        setMinimalGridSize(int(settings.minimalGridSize)); // FIXME: int vs uint
        setGridCoarseMultiplier(int(settings.coarseGridMultiplier));
        if (settings.solidCoarseGridLinesEnabled)
        {
            setGridCoarseLineStyle(Qt::SolidLine);
        }
        else
        {
            setGridCoarseLineStyle(Qt::DotLine);
        }
        if (settings.solidFineGridLinesEnabled)
        {
            setGridFineLineStyle(Qt::SolidLine);
        }
        else
        {
            setGridFineLineStyle(Qt::DotLine);
        }

        if (!settings.scrollBarsEnabled)
        {
            setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        }
        else if (settings.scrollBarsAsNeededEnabled)
        {
            setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
            setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        }
        else
        {
            setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
            setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
        }
        if (!settings.cursorCrosshairEnabled)
        {
            setMouseCursor(NoMouseCursor);
        }
        else if (settings.largeCursorCrosshairEnabled)
        {
            setMouseCursor(LargeMouseCursor);
        }
        else
        {
            setMouseCursor(SmallMouseCursor);
        }
        if (!settings.originCrosshairEnabled)
        {
            setOriginMark(NoOriginMark);
        }
        else if (settings.largeOriginCrosshairEnabled)
        {
            setOriginMark(LargeOriginMark);
        }
        else
        {
            setOriginMark(SmallOriginMark);
        }
        setRenderHint(QPainter::Antialiasing, settings.antiAliasingEnabled);
        setRenderHint(QPainter::TextAntialiasing, settings.antiAliasingEnabled);
        setRenderHint(QPainter::HighQualityAntialiasing, settings.antiAliasingEnabled);
        setHardwareAccelerationEnabled(settings.hardwareAccelerationEnabled);

    }

    void View::setupView()
    {
        setContextMenuPolicy(Qt::CustomContextMenu);
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        setDragMode(QGraphicsView::NoDrag);
        setMouseTracking(true);
        setTransformationAnchor(NoAnchor);
        setResizeAnchor(NoAnchor);
        setViewportUpdateMode(FullViewportUpdate);
        // FIXME: requires to fix ruler range handling
    #if 0
        // x= x, y = -y
        setTransform(QTransform(1, 0, 0,
                                0, -1, 0,
                                0, 0, 1));
    #endif
    }


    void View::setupGrid()
    {
        m_grid->setGridModel(m_gridModel);
        m_horizontalRuler->setGridModel(m_gridModel);
        m_verticalRuler->setGridModel(m_gridModel);
    }

    void View::setupLayout()
    {
        QGridLayout *layout = new QGridLayout;
        layout->setSpacing(0);
        layout->setMargin(0);
        m_topLeftWidget->setFixedSize(RulerBar::BREADTH, RulerBar::BREADTH);
        m_topLeftWidget->hide();
        m_horizontalRuler->setFixedHeight(RulerBar::BREADTH);
        m_horizontalRuler->hide();
        m_topRightWidget->setFixedSize(0, RulerBar::BREADTH);
        m_topRightWidget->hide();
        m_verticalRuler->setFixedWidth(RulerBar::BREADTH);
        m_verticalRuler->hide();
        m_bottomLeftWidget->setFixedSize(RulerBar::BREADTH, 0);
        m_bottomLeftWidget->hide();
        horizontalScrollBar()->installEventFilter(this);
        verticalScrollBar()->installEventFilter(this);
        layout->addWidget(m_topLeftWidget, 0, 0);
        layout->addWidget(m_horizontalRuler, 0, 1);
        layout->addWidget(m_topRightWidget, 0, 2);
        layout->addWidget(m_verticalRuler, 1, 0);
        layout->addWidget(m_bottomLeftWidget, 2, 0);
        layout->addWidget(viewport(), 1, 1);
        setLayout(layout);
    }

    void View::applyDefaultSettings()
    {
        // TODO: applySettings(Settings());
        setHardwareAccelerationEnabled(false);
        setRulerEnabled(true);
        setGridEnabled(true);
        setMinimalGridSize(10);
        setGridCoarseMultiplier(10);
        setGridCoarseLineStyle(Qt::SolidLine);
        setGridFineLineStyle(Qt::DotLine);
        setMouseCursor(LargeMouseCursor);
        setOriginMark(SmallOriginMark);
    }
}
